---
sidebar_position: 4
---

# 4. Контейнер list

## Основные сведения

Шаблонный класс `list` входит в библиотеку STL. В библиотеке STL описывается шаблонный класс `list`, который позволяет описывать списки переменных любого типа. Формальное определение шаблона дано ниже:

```cpp
template <class Type, class Allocator = std::allocator<Type>>
class list {
  // ...
};
```

Для работы объектами этого класса необходимо подключить заголовочный файл:

```cpp
#include <list>
```

## Описание списка

Для описания списков предусмотрены разные конструкторы. Примеры описания списков даны ниже:

```cpp
// Списки list описания и инициализация
std::list<int> l0;  // Пустой список l0
std::list<int> l1(3);  // Список с тремя элементами равными 0
std::list<int> l2(5, 2);  // Список из пяти элементов равными 2
std::list<int> l3(l2);  // Список l3 на основе списка l2
std::list<int>::iterator l3_Iter;  // Описание итератора l3_Iter
l3_Iter = l3.begin();  // Итераторные вычисления на начало
l3_Iter++;
l3_Iter++;  // Итераторные вычисления продвинуть на два объекта
std::list<int> l4(l3.begin(),
                  l3_Iter);  // Список l4 на основе первых двух элементов L3
```

## Итерирование по списку

Функцию печати опишем следующим образом:

```cpp
void lPrint(std::list<int>& l) {
  std::list<int>::iterator iter;
  int i = 0;
  if (!l.empty()) {
    for (iter = l.begin(); iter != l.end(); iter++, i++) {
      std::cout << "l[" << i << "] = " << *iter << std::endl;
    }
  } else
    std::cout << "Список пуст!" << std::endl;
}
```

Для описания пустого списка `l` (типа `list`) также нужно указать тип `int` (инстанцировать шаблонный объект):

```cpp
#include <iostream>
#include <list>

void lPrint(std::list<int>& l) {
  std::list<int>::iterator iter;
  int i = 0;
  if (!l.empty()) {
    for (iter = l.begin(); iter != l.end(); iter++, i++) {
      std::cout << "l[" << i << "] = " << *iter << std::endl;
    }
  } else
    std::cout << "Список пуст!" << std::endl;
}

int main() {
#ifdef WIN32
  system("chcp 65001");
#else
  setlocale(LC_ALL, "Russian");
#endif
  std::list<int> l;  // Пустой список l
  std::cout << "Добавление:" << std::endl;
  l.push_back(1);   // Добавление в конец списка
  l.push_back(2);   // Добавление в конец списка
  l.push_front(5);  // Добавление в начало списка
  lPrint(l);  // Собственная функция печати целого списка
}
```

В функции печати используется итератор `iter`, который позволяет продвигаться по списку (`iter++`). Для установки итератора на первый элемент используется метод `begin`. Остановка просмотра проверяется методом end. Для проверки пустого списка используется метод `empty`. Для дальнейшей демонстрации возможностей списков будем использовать эту функцию. В первом фрагменте получим результат:

```bash
Добавление:
l[0] = 5
l[1] = 1
l[2] = 2
```

## Слияние списков

Рассмотрим еще несколько примеров использования объектов класса список и его методов. Для копирования списков может быть использован метод слияния (`assign`), а для очистки списка может быть использован метод удаления (`erase`  или `clear`):

```cpp
// Копирование и очистка
std::list<int> l20;  // Пустой список l
l20.erase(l20.begin(), l20.end());
lPrint(l20);
l20.assign(l.begin(), l.end());
std::cout << "После копирования l в l20:" << std::endl;
lPrint(l20);
l20.clear();
std::cout << "После очистки l20:" << std::endl;
lPrint(l20);
```

В этом фрагменте получим результат:

```bash
Список пуст!
После копирования l в l20:
l[0] = 5
l[1] = 1
l[2] = 2
После очистки l20:
Список пуст!
```

## Определения размера списка

Получить текущую размерность списка можно методом `size`:

```cpp
std::cout << "Число элементов в списке =  " << l.size() << std::endl;
```

Получим результат:

```bash
Число элементов в списке =  3
```

## Манипулировать с содержимым списка (вставка, удаление и т.д.)

Манипулировать с содержимым списка (вставка, удаление и т.д.) можно с помощью методов (`insert`, `remove`, `erase`, `remove_if` и др.). Ниже приводиться работающий фрагмент программы, иллюстрирующий эти действия (комментарии даны в тексте программы):

```cpp
#include <iostream>
#include <list>

void lPrint(std::list<int>& l) {
  std::list<int>::iterator iter;
  int i = 0;
  if (!l.empty()) {
    for (iter = l.begin(); iter != l.end(); iter++, i++) {
      std::cout << "l[" << i << "] = " << *iter << std::endl;
    }
  } else
    std::cout << "Список пуст!" << std::endl;
}

int main() {
#ifdef WIN32
  system("chcp 65001");
#else
  setlocale(LC_ALL, "Russian");
#endif

  std::list<int> l;  // Пустой список l
  std::cout << "Добавление:" << std::endl;
  l.push_back(1);   // Добавление в конец списка
  l.push_back(2);   // Добавление в конец списка
  l.push_front(5);  // Добавление в начало списка
  lPrint(l);  // Собственная функция печати целого списка

  // Вставка
  std::list<int>::iterator iter = l.begin();
  iter++;
  iter++;  // Вставка после второго
  std::cout << "Вставка:" << std::endl;
  l.insert(iter, 100);
  lPrint(l);
  // Удаление по числу
  l.remove(100);
  std::cout << "Удаление по числу 100:" << std::endl;
  lPrint(l);
  // Удаление по номеру
  l.erase(l.begin());  // Из головы списка
  std::cout << "Удаление по номеру 0:" << std::endl;
  lPrint(l);
  // Добавим и удалим по итератору
  l.push_back(20);
  l.push_back(31);
  std::cout << "Перед удалением:" << std::endl;
  lPrint(l);
  iter = l.begin();
  iter++;
  iter++;  // Сдвиг на два элемента
  l.erase(iter);
  std::cout << "Удаление по итератору 2 (3-й элемент):" << std::endl;
  // Удаление по условию (предикату)
  lPrint(l);
  std::list<int> l22 = l;
  std::cout << "Удаление четных чисел:" << std::endl;
  std::cout << "-> До удаления:" << std::endl;
  lPrint(l22);
  auto is_odd = [](int const elem) { return (elem % 2); };
  l22.remove_if(is_odd);
  std::cout << "-> После удаления:" << std::endl;
  lPrint(l22);
}
```

Результат выполнения:

```bash
Добавление:
l[0] = 5
l[1] = 1
l[2] = 2
Вставка:
l[0] = 5
l[1] = 1
l[2] = 100
l[3] = 2
Удаление по числу 100:
l[0] = 5
l[1] = 1
l[2] = 2
Удаление по номеру 0:
l[0] = 1
l[1] = 2
Перед удалением:
l[0] = 1
l[1] = 2
l[2] = 20
l[3] = 31
Удаление по итератору 2 (3-й элемент):
l[0] = 1
l[1] = 2
l[2] = 31
Удаление четных чисел:
-> До удаления:
l[0] = 1
l[1] = 2
l[2] = 31
-> После удаления:
l[0] = 2
```

Обратим внимание на строчки:

```cpp
auto is_odd = [](int const elem) { return (elem % 2); };
l22.remove_if(is_odd);
```

Для определения элементов, которые подлежат удалению, используется именованное лямда-выражение. Можно также использовать экземпляр шаблонного класса, как показано ниже:

```cpp
template <class T>
class is_odd : public std::unary_function<T, bool> {
 public:
  bool operator()(T& val) { return (val % 2) != 1; }
};  // Условие предиката – четное = 0

// ...
l22.remove_if(is_odd<int>());
```

Это позволяет установить для каждого элемента списка значение условия для его удаления. В нашем случае выполняется проверка на честность параметра элемента, передаваемого в функцию. В при выполнении [индивидуальных заданий](../IndividualTask/intro.md) необходимо для своего целого списка объявить шаблонный класс для условия удаления элементов с заданным значением переменной.

## Сортировка списка

Сортировка списка выполняется методом sort, при этом может быть указан параметр или не указан. Если параметр не задан, то сортировка выполняется в порядке возрастания ключевой переменной (`less`). При задании параметра сортировки `greater` сортировка выполняется в порядке убывания основной переменной списка. Примеры и результат приведены ниже.

```cpp
#include <algorithm>
#include <iostream>
#include <list>

void lPrint(std::list<int>& l) {
  std::list<int>::iterator iter;
  int i = 0;
  if (!l.empty()) {
    for (iter = l.begin(); iter != l.end(); iter++, i++) {
      std::cout << "l[" << i << "] = " << *iter << std::endl;
    }
  } else
    std::cout << "Список пуст!" << std::endl;
}

int main() {
#ifdef WIN32
  system("chcp 65001");
#else
  setlocale(LC_ALL, "Russian");
#endif

  std::list<int> l;  // Пустой список l
  std::cout << "Добавление:" << std::endl;
  l.push_back(1);   // Добавление в конец списка
  l.push_back(2);   // Добавление в конец списка
  l.push_front(5);  // Добавление в начало списка
  lPrint(l);  // Собственная функция печати целого списка

  // Сортировка
  std::cout << "До сортировки списка!" << std::endl;
  lPrint(l);
  l.sort(std::greater<int>());  // Сортировка по убыванию
  std::cout << "После greater сортировки списка!" << std::endl;
  lPrint(l);
  l.sort(std::less<int>());  // Явная сортировка по возрастанию
  std::cout << "После less сортировки списка!" << std::endl;
  lPrint(l);
  // Список l22
  std::list<int> l22 = l;
  std::cout << "До сортировки списка (l22)!" << std::endl;
  l22.push_front(555);  // Добавление в начало списка
  lPrint(l22);
  l22.sort();  // Сортировка по возрастанию по умолчанию ( возрастание) less
  std::cout << "После сортировки списка (по умолчанию)!" << std::endl;
  lPrint(l22);
}
```

Получим результат:

```bash
Добавление:
l[0] = 5
l[1] = 1
l[2] = 2
До сортировки списка!
l[0] = 5
l[1] = 1
l[2] = 2
После greater сортировки списка!
l[0] = 5
l[1] = 2
l[2] = 1
После less сортировки списка!
l[0] = 1
l[1] = 2
l[2] = 5
До сортировки списка (l22)!
l[0] = 555
l[1] = 1
l[2] = 2
l[3] = 5
После сортировки списка (по умолчанию)!
l[0] = 1
l[1] = 2
l[2] = 5
l[3] = 555
```

Переупорядочение списка, изменение порядка на обратный, выполняется методом `reverse`. Пример:

```cpp
// ...
std::cout << "До сортировки списка!" << std::endl;
lPrint(l);
std::cout << "После reverse сортировки списка!" << std::endl;
l.reverse();
lPrint(l);
```

Получим результат:

```bash
До сортировки списка!
l[0] = 5
l[1] = 1
l[2] = 2
После reverse сортировки списка!
l[0] = 2
l[1] = 1
l[2] = 5
```

## Очистка списка

Очистка списка (`clear`):

```cpp
// ...
l.clear();
std::cout << "После очистки списка!" << std::endl;
lPrint(l);
```

Получим результат:

```bash
После очистки списка!
Пустой список!
```

## Использование класса list для включения пользовательских объектов

При выполнении [индивидуальных заданий](../IndividualTask/intro.md) необходимо продемонстрировать использование методов класса список  - `list` для простых объектов класса, который определяется вариантом задания. Отличие заключается в том, что здесь используются нестандартные переменные (нестандартный тип `int`)

### Определение класса

Использование своего класса в качестве содержимого списка имеет ряд особенностей. На примере простого класса `Point` рассмотрим их. Заголовочный файл данного класса:

```cpp
// point.h
#ifndef POINT_H
#define POINT_H
#include <iostream>
#include <list>

class Point {
 public:
  int x;
  int y;
  Point();
  Point(int a, int b);
  Point(const Point& p);
  Point& operator=(const Point& p);
  friend std::ostream& operator<<(std::ostream& out, Point& obj);
  friend bool operator==(const Point& p1, const Point& p2);
  friend bool operator>(const Point& p1, const Point& p2);
  friend bool operator<(const Point& p1, const Point& p2);
};

template <class T>
void lPPrint(std::list<T>& l) {
  typedef typename std::list<T>::iterator iterator;

  iterator iter;
  int i = 0;
  if (!l.empty()) {
    for (iter = l.begin(); iter != l.end(); iter++, i++) {
      std::cout << "list[" << i << "] = " << *iter << std::endl;
    }
  } else
    std::cout << "Список пуст!" << std::endl;
}
#endif  // POINT_H
```

Реализация данного класса:

```cpp
// point.cpp
#include "point.h"

Point::Point() {
  x = 0;
  y = 0;
};

Point::Point(int a, int b) {
  x = a;
  y = b;
};

Point::Point(const Point& p) {
  x = p.x;
  y = p.y;
};

Point& Point::operator=(const Point& p) {
  x = p.x;
  y = p.y;
  return *this;
};

// Перегурузка операции для сортировки
bool operator<(const Point& p1, const Point& p2) { return (p1.x < p2.x); };

// Перегурузка операции для сортировки
bool operator>(const Point& p1, const Point& p2) { return (p1.x > p2.x); };

// Перегурузка операции сравнения для удаления по объекту
bool operator==(const Point& p1, const Point& p2) {
  return ((p1.x == p2.x) && (p1.y == p2.y));
};

// Перегурузка операции для увывода объекта
std::ostream& operator<<(std::ostream& out, Point& obj) {
  out << "{ x = " << obj.x << " y = " << obj.y << " } ";
  return out;
};
```

### Инстанцирование списка и его итераторы

После этого мы можем описать разнообразные списки этих объектов, используя инстанцирование и итераторы. Ниже приведены примеры вариантов использования конструкторов:

```cpp
#include "point.h"

int main() {
#ifdef WIN32
  system("chcp 65001");
#else
  setlocale(LC_ALL, "Russian");
#endif
  std::list<Point> lP;
  std::list<Point>::iterator PIter;
  Point P1(11, 22);
  Point P2(51, 52);
  std::cout << "КЛАСС POINT!!!" << std::endl;
  lP.push_back(P1);
  lP.push_back(P2);
  lP.push_front(*new Point(31, 32));
  lPPrint<Point>(lP);
}
```

Тогда получим следующий результат:

```bash
КЛАСС POINT!!!
list[0] = { x = 31 y = 32 } 
list[1] = { x = 11 y = 22 } 
list[2] = { x = 51 y = 52 }
```

Можно описать списки на основе разных конструкторов, а также итератор для навигации по ним:

```cpp
// ...
std::list<Point> lp;  // Пустой список lp0
std::list<Point> lp1(3);  // Список с тремя элементами равными 0
std::list<Point> lp2(5, P2);  // Список из пяти элементов равными P2
std::list<Point> lp3(lp2);  // Список l3 на основе списка l2
std::list<Point>::iterator lp3_Iter;
lp3_Iter = lp3.begin();
lp3_Iter++;
lp3_Iter++;
std::list<Point> lp4(
    lp3.begin(),
    lp3_Iter);  // Новый список lp4 на основе первых двух элементов lp3
std::cout << "КОНСТРУКТОРЫ:  КЛАСС POINT!!!" << std::endl;
std::cout << "lp:" << std::endl;
lPPrint(lp);
std::cout << "lp1:" << std::endl;
lPPrint(lp1);
std::cout << "lp2:" << std::endl;
lPPrint(lp2);
std::cout << "lp3:" << std::endl;
lPPrint(lp3);
std::cout << "lp4:" << std::endl;
lPPrint(lp4);
```

Получим после выполнения этого фрагмента программы:

```bash
КОНСТРУКТОРЫ:  КЛАСС POINT!!!
lp:
Список пуст!
lp1:
list[0] = { x = 0 y = 0 } 
list[1] = { x = 0 y = 0 } 
list[2] = { x = 0 y = 0 } 
lp2:
list[0] = { x = 51 y = 52 } 
list[1] = { x = 51 y = 52 } 
list[2] = { x = 51 y = 52 } 
list[3] = { x = 51 y = 52 } 
list[4] = { x = 51 y = 52 } 
lp3:
list[0] = { x = 51 y = 52 } 
list[1] = { x = 51 y = 52 } 
list[2] = { x = 51 y = 52 } 
list[3] = { x = 51 y = 52 } 
list[4] = { x = 51 y = 52 } 
lp4:
list[0] = { x = 51 y = 52 } 
list[1] = { x = 51 y = 52 } 
```

### Манипулирование содержимым списка

Все другие методы класса `list` из STL также можно использовать для построения списков на основе класса `Point`. Рассмотрим и другие примеры. Метод позволяет `assign` добавить в новый список элементы из другого списка, а метод `clear`, очистить список:

```cpp
// ...
// Копирование и очистка
// list <Point> lp20; // - Можно и так описать список lp20
typedef std::list<Point>
    ListPoint;  // Зададим новый тип - ListPoint список точек
ListPoint lp20;
std::cout << "Исходный список lP:" << std::endl;
lPPrint(lP);  // см. выше пример
lPPrint(lp20);
lp20.assign(lP.begin(),
            --lP.end());  // копируем без последнего элемента списка
std::cout << "После копирования lP в lp20:" << std::endl;
lPPrint(lp20);
lp20.clear();
std::cout << "После очистки lp20:" << std::endl;
lPPrint(lp20);
```

Получим результат, при копировании всего списка без последнего элемента (`--lP.end()`):

```bash
Исходный список lP:
list[0] = { x = 31 y = 32 } 
list[1] = { x = 11 y = 22 } 
list[2] = { x = 51 y = 52 } 
Список пуст!
После копирования lP в lp20:
list[0] = { x = 31 y = 32 } 
list[1] = { x = 11 y = 22 } 
После очистки lp20:
Список пуст!
```

Манипулировать с содержимым списка (вставка, удаление и т.д.) можно с помощью методов, рассмотренных выше для стандартных типов: `insert`, `erase`, `remove_if` и др. Примеры:

```cpp
// ...
// Копирование и очистка
// list <Point> lp20; // - Можно и так описать список lp20
// Вставка по итератору
PIter = lP.begin();
PIter++;
PIter++;  // итератор переместим на 2 элемента
std::cout << "Вставка: до вставки 2-го:" << std::endl;
lPPrint(lP);
std::cout << "Вставка: после вставки 2-го:" << std::endl;
lP.insert(PIter, P1);  // 11-22
lPPrint(lP);
// Удаление по итератору
lP.erase(lP.begin());
lP.push_back(*new Point(20, 62));
std::cout << "Удаление по итератору (0 - первого):" << std::endl;
lPPrint(lP);
// Удаление по простой предикат для удаления х = 20
lP.remove_if(std::function<bool(Point)>(
    [](Point val) { return val.x == 20; }));  // Работает!!!
std::cout << "После удаления x = 20 списка!" << std::endl;
lPPrint(lP);
```

В результате выполнения данного фрагмента программы получим:

```bash
Вставка: до вставки 2-го:
list[0] = { x = 31 y = 32 } 
list[1] = { x = 11 y = 22 } 
list[2] = { x = 51 y = 52 } 
Вставка: после вставки 2-го:
list[0] = { x = 31 y = 32 } 
list[1] = { x = 11 y = 22 } 
list[2] = { x = 11 y = 22 } 
list[3] = { x = 51 y = 52 } 
Удаление по итератору (0 - первого):
list[0] = { x = 11 y = 22 } 
list[1] = { x = 11 y = 22 } 
list[2] = { x = 51 y = 52 } 
list[3] = { x = 20 y = 62 } 
После удаления x = 20 списка!
list[0] = { x = 11 y = 22 } 
list[1] = { x = 11 y = 22 } 
list[2] = { x = 51 y = 52 } 
```

Функция `remove` (удалить по объекту) требует описания перегруженной операции равенства, присваивания и явных конструкторов копирования (см. реализацию [выше](#определение-класса)). Тогда можно выполнить следующий фрагмент текста с функцией `remove`:

```cpp
// ...
// Вставка по итератору
PIter = lP.begin();
PIter++;
PIter++;  // итератор переместим на 2 элемента
std::cout << "Вставка: до вставки 2-го:" << std::endl;
lPPrint(lP);
std::cout << "Вставка: после вставки 2-го (P1):" << std::endl;
lP.insert(PIter, P1);  // <11,22>
lPPrint(lP);
std::cout << "Размер списка:" << lP.size() << std::endl;  // Размер списка
lP.insert(PIter, P2);
lP.insert(PIter, *new Point(51, 61));
std::cout << "Вставка: после вставки 2-го(P2 вставка 2-x):" << std::endl;
lPPrint(lP);
lP.remove(P2);
std::cout << "Удаление по объекту P2 = <51,52> (после):" << std::endl;
lPPrint(lP);
```

В результате выполнения данного фрагмента программы получим (удаляются точки с координатами `<51,52>`):

```cpp
Вставка: до вставки 2-го:
list[0] = { x = 31 y = 32 } 
list[1] = { x = 11 y = 22 } 
list[2] = { x = 51 y = 52 } 
Вставка: после вставки 2-го (P1):
list[0] = { x = 31 y = 32 } 
list[1] = { x = 11 y = 22 } 
list[2] = { x = 11 y = 22 } 
list[3] = { x = 51 y = 52 } 
Размер списка:4
Вставка: после вставки 2-го(P2 вставка 2-x):
list[0] = { x = 31 y = 32 } 
list[1] = { x = 11 y = 22 } 
list[2] = { x = 11 y = 22 } 
list[3] = { x = 51 y = 52 } 
list[4] = { x = 51 y = 61 } 
list[5] = { x = 51 y = 52 } 
Удаление по объекту P2 = <51,52> (после):
list[0] = { x = 31 y = 32 } 
list[1] = { x = 11 y = 22 } 
list[2] = { x = 11 y = 22 } 
list[3] = { x = 51 y = 61 } 
```

Функции `sort` требуют описания перегруженных операций сравнения (`>`,`<`) для объекта типа `Point`.  Для упрощения предиката зададим сортировку только по параметру класса `x` (см. реализацию [выше](#определение-класса)).

Пример использования метода `sort` показан ниже. Используемый ниже метод `reverse` позволяет изменить порядок расположения объектов на обратный порядок. После этого можно выполнить следующий фрагмент программы:

```cpp
// ...
// Сортировка
lP.push_front(*new Point(20, 32));
std::cout << "Обратный порядок (до):" << std::endl;
lPPrint(lP);
lP.reverse();  // Изменение порядка на обратный
std::cout << "Обратный порядок (после):" << std::endl;
lPPrint(lP);
std::cout << "Сортировка по возрастанию х:" << std::endl;
lP.sort();  // По умолчанию less - сортировкапо возрастанию
lPPrint(lP);
std::cout << "Сортировка по убыванию х:" << std::endl;
lP.sort(std::greater<Point>());
lPPrint(lP);
```

Получим в результате:

```cpp
Обратный порядок (до):
list[0] = { x = 20 y = 32 } 
list[1] = { x = 31 y = 32 } 
list[2] = { x = 11 y = 22 } 
list[3] = { x = 51 y = 52 } 
Обратный порядок (после):
list[0] = { x = 51 y = 52 } 
list[1] = { x = 11 y = 22 } 
list[2] = { x = 31 y = 32 } 
list[3] = { x = 20 y = 32 } 
Сортировка по возрастанию х:
list[0] = { x = 11 y = 22 } 
list[1] = { x = 20 y = 32 } 
list[2] = { x = 31 y = 32 } 
list[3] = { x = 51 y = 52 } 
Сортировка по убыванию х:
list[0] = { x = 51 y = 52 } 
list[1] = { x = 31 y = 32 } 
list[2] = { x = 20 y = 32 } 
list[3] = { x = 11 y = 22 } 
```

С использованием других предикатов предлагается разобраться самостоятельно. Особенностью использования собственных объектов является необходимость перегрузки операций вывода. Другой особенностью класса `list` является автоматическое корректное удаление динамических объектов, созданных в программе и включенных в массив.
